import { unlink } from "node:fs/promises";
import { join } from "node:path";

import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { Request } from "express";

import { CONFIG_KEY } from "@/core/config/configurations";
import { PrismaService } from "@/core/db/prisma.service";
import BusinessException from "@/core/exception/BusinessException";
import { FILE_DOWNLOAD_ROUTE_PREFIX } from "@/core/file/file.constant";
import { FileUtils } from "@/core/utils/file.utils";
import { PathUtils } from "@/core/utils/path.utils";
import { RequestUrlUtil } from "@/core/utils/request-url.utils";
import { PageResultVO } from "@/core/vo/page-result.vo";

import { CreateFileDto } from "../dto/create-file.dto";
import { QueryFileDto } from "../dto/query-file.dto";
import { UpdateFileDto } from "../dto/update-file.dto";
import { UploadFileVO } from "../vo/upload-file.vo";

@Injectable()
export class FileService {
    constructor(
        private readonly prismaService: PrismaService,
        private readonly configService: ConfigService
    ) {}

    /**
     * 将上传文件的信息保存到数据库中
     * @param req 请求对象
     * @param createFileDto 额外的文件信息
     * @param files 文件基础信息列表
     * @returns UploadFileVO[] 上传文件信息列表
     */
    async createFilesRecord(
        req: Request,
        createFileDto: CreateFileDto,
        files: Express.Multer.File[]
    ): Promise<UploadFileVO[]> {
        const userId = req.user.id;
        const baseUrl = RequestUrlUtil.getBaseUrl(req);
        const { dirId, origin, hash, bizId } = createFileDto;

        const fileDir = await this.prismaService.fileDirectory.findUnique({ where: { id: dirId, isDeleted: false } });
        if (!fileDir) throw new BusinessException("DATA_NOT_FOUND", "目录不存在");

        const fileList = await this.prismaService.uploadFile.findMany({
            where: {
                hash: { in: [hash] },
                isDeleted: false
            }
        });
        if (fileList.length > 0) throw new BusinessException("FILE_ALREADY_EXISTS");

        const _successFilesCount = await this.prismaService.uploadFile.createMany({
            data: files.map(file => ({
                dirId,
                userId: userId,
                fileType: FileUtils.categorizeFileByMimetype(file.mimetype),
                origin,
                name: file.originalname.length > 100 ? file.originalname.slice(-100) : file.originalname,
                uri: FILE_DOWNLOAD_ROUTE_PREFIX + file.filename,
                hash,
                bizId,
                ext: file.originalname.split(".").pop(),
                size: file.size + ""
            }))
        });
        const { appServerUrlPrefix } = this.configService.get(CONFIG_KEY.server);
        return files.map(file => ({
            dirId,
            fileType: FileUtils.categorizeFileByMimetype(file.mimetype),
            originalName: file.originalname.length > 100 ? file.originalname.slice(-100) : file.originalname,
            size: file.size,
            path: FILE_DOWNLOAD_ROUTE_PREFIX + file.filename,
            url: PathUtils.joinUrl(baseUrl, appServerUrlPrefix, FILE_DOWNLOAD_ROUTE_PREFIX, file.filename)
        }));
    }

    /**
     * 根据查询条件查询文件列表
     * @param queryFileDto 查询条件
     * @returns 文件列表分页信息
     */
    async queryFile(queryFileDto: QueryFileDto, req: Request) {
        const { current, size, createdAtStart, createdAtEnd, dirId, origin, userId, fileType, name } = queryFileDto;
        const where = {
            isDeleted: false,
            dirId: dirId,
            origin: origin,
            userId: userId,
            fileType: fileType,
            name: { contains: name },
            createdAt: { gte: createdAtStart, lte: createdAtEnd }
        };
        const [listData, total] = await Promise.all([
            this.prismaService.uploadFile.findMany({
                skip: (current - 1) * size,
                take: size,
                where,
                orderBy: { createdAt: "desc" }
            }),
            this.prismaService.uploadFile.count({
                where
            })
        ]);
        const pages = Math.ceil(total / size);
        const { appServerUrlPrefix } = this.configService.get(CONFIG_KEY.server);
        const baseUrl = RequestUrlUtil.getBaseUrl(req);
        const listDataVO = listData.map((item: any) => {
            item.url = PathUtils.joinUrl(baseUrl, appServerUrlPrefix, item.uri);
            return item;
        });
        return new PageResultVO(listDataVO, total, pages, current, size);
    }

    /**
     * 根据文件ID查询文件信息
     * @param id 文件ID
     * @returns 文件信息
     */
    async findOne(id: number) {
        return await this.prismaService.uploadFile.findUnique({
            where: {
                id,
                isDeleted: false
            }
        });
    }

    /**
     * 更新文件信息
     * @param id 文件ID
     * @param updateFileDto 更新文件信息
     * @returns 更新后的文件信息
     */
    async update(id: number, updateFileDto: UpdateFileDto, req: Request) {
        let file;
        try {
            file = await this.prismaService.uploadFile.update({
                where: {
                    id,
                    isDeleted: false
                },
                data: updateFileDto
            });
        } catch (error) {
            throw new BusinessException("OPERATION_FAILED");
        }
        const { appServerUrlPrefix } = this.configService.get(CONFIG_KEY.server);
        const baseUrl = RequestUrlUtil.getBaseUrl(req);
        file.url = PathUtils.joinUrl(baseUrl, appServerUrlPrefix, file.uri);
        return file;
    }

    /**
     * 批量删除文件
     * @param ids 文件ID列表
     */
    async removeBatch(ids: number[]) {
        // 1. 查询要删除的文件记录
        const filesToDelete = await this.prismaService.uploadFile.findMany({
            where: {
                id: { in: ids },
                isDeleted: false
            },
            select: {
                id: true,
                uri: true,
                fileType: true
            }
        });
        if (filesToDelete.length === 0) throw new BusinessException("DATA_NOT_FOUND");

        const { dest } = this.configService.get(CONFIG_KEY.upload);

        // 2. 开启事务保证原子性
        await this.prismaService.$transaction(async tx => {
            // 3. 删除数据库记录
            const _deleteResult = await tx.uploadFile.deleteMany({
                where: {
                    id: { in: filesToDelete.map(f => f.id) }
                }
            });

            // 4. 并行删除物理文件（带错误收集）
            const deletePromises = filesToDelete.map(async file => {
                const fullPath = join(dest, file.fileType, file.uri.split(FILE_DOWNLOAD_ROUTE_PREFIX)[1]);
                try {
                    await unlink(fullPath);
                    return { success: true, file };
                } catch (error) {
                    if (error.code === "ENOENT") {
                        // console.warn(`文件已不存在: ${fullPath}`);
                        return { success: true, file }; // 视为成功
                    }
                    // console.error(`删除失败: ${fullPath}`, error);
                    return { success: false, file, error };
                }
            });

            // 5. 等待所有删除操作完成
            const results = await Promise.all(deletePromises);

            // 6. 处理部分失败情况
            const failedDeletes = results.filter(r => !r.success);
            if (failedDeletes.length > 0) {
                throw new BusinessException(
                    "FILE_DELETE_ERROR",
                    `${failedDeletes.length}个文件删除失败，已回滚数据库操作`
                );
            }
        });
    }

    /**
     * 根据文件ID删除文件
     * @param id 文件ID
     * @param softDelete 是否软删除，默认true
     */
    async remove(id: number, softDelete: boolean = true) {
        try {
            if (softDelete) {
                await this.prismaService.uploadFile.update({
                    where: {
                        id,
                        isDeleted: false
                    },
                    data: {
                        isDeleted: true,
                        deletedAt: new Date()
                    }
                });
            } else {
                await this.removeBatch([id]);
            }
        } catch (error) {
            throw new BusinessException();
        }
    }

    /**
     * 移动文件到指定目录
     * @param ids 文件ID列表
     * @param dirId 目标目录ID
     */
    async moveFile(ids: number[], dirId: number) {
        try {
            await this.prismaService.uploadFile.updateMany({
                where: {
                    id: { in: ids },
                    isDeleted: false
                },
                data: {
                    dirId
                }
            });
        } catch (error) {
            throw new BusinessException("OPERATION_FAILED");
        }
    }
}
